package cc.jq1024.config;

import cc.jq1024.types.annotations.DCCValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.zookeeper.data.Stat;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * 项目启动时，获取zk连接以便后续监听
 * 同时获取 @DCCValue 注解的相关信息
 *
 * @author li--jiaqiang
 * @date 2024−07−30
 */
@Slf4j
@Configuration
public class DCCValueBeanFactory implements BeanPostProcessor {

    /**
     * 对象cache - 后续优化为分布式缓存
     */
    private final Map<String, Object> dccObjGroup = new HashMap<>();

    /**
     * zk 节点基础路径
     */
    private static final String BASE_CONFIG_PATH = "/big-market-dcc";
    private static final String BASE_CONFIG_PATH_CONFIG = BASE_CONFIG_PATH + "/config";

    /**
     * 是 Apache Curator 的一个客户端实例，用于与 ZooKeeper 进行交互。
     */
    private final CuratorFramework zkClient;

    public DCCValueBeanFactory(CuratorFramework client) throws Exception {
        this.zkClient = client;

        // 节点判断,不存在就创建（根路径）
        if (null == client.checkExists().forPath(BASE_CONFIG_PATH_CONFIG)) {
            client.create().creatingParentsIfNeeded().forPath(BASE_CONFIG_PATH_CONFIG);
            log.info("DCC 节点监听 base node 【{}】 not absent create new done!", BASE_CONFIG_PATH_CONFIG);
        }
        log.info("DCC 节点监听 init done!");

        /*
         * zk固定代码：
         *
         * CuratorCache：
         * 这是Curator中的一个特性，它为ZooKeeper中的数据提供了一个缓存机制。它监听指定ZNode（ZooKeeper节点）的变化，
         * 并相应地更新缓存。这很有用，因为它允许应用程序使用本地数据副本，这可以提高性能并减少网络负载。
         *
         * CuratorCache.build(client, BASE_CONFIG_PATH_CONFIG)：是一个工厂方法，用于构建新的CuratorCache实例。
         *
         * curatorCache.start()：这个方法启动缓存。在可以使用缓存监听变化或查询数据之前，
         *                      必须调用start()方法。这个方法初始化缓存，并开始监听指定ZNode的变化。
         */
        CuratorCache curatorCache = CuratorCache.build(client, BASE_CONFIG_PATH_CONFIG);
        curatorCache.start();

        /*
         * 添加节点监听器:
         * type：这是一个枚举值，表示发生了什么类型的事件。
         *      它可以是NODE_CREATED（节点创建）、NODE_CHANGED（节点数据改变）、NODE_DELETED（节点删除）。
         * oldData：这是一个包含变化前节点数据的ChildData对象。如果事件是创建事件，这个值可能为null。
         * newData：这是一个包含变化后节点数据的ChildData对象。如果事件是删除事件，这个值可能为null
         */
        curatorCache.listenable().addListener((type, oldData, newData) -> {

            switch (type) {
                case NODE_CHANGED:
                    // 获取变化数据的path路径
                    String dccNewValuePath = newData.getPath();
                    Object newObjectBean = dccObjGroup.get(dccNewValuePath);
//                    Object newObjectBean = dccObjGroup.get(dccNewValuePath);
                    // 获取对应的bean
                    if (null == newObjectBean) return;
                    try {
                        // 处理AOP代理类
                        Class<?> objectBeanClass = newObjectBean.getClass();
                        if (AopUtils.isAopProxy(objectBeanClass))
                            objectBeanClass = AopUtils.getTargetClass(objectBeanClass);
                        // 获取字段名
                        String filedName = dccNewValuePath.substring(dccNewValuePath.lastIndexOf("/") + 1);
                        // 获取字段
                        Field declaredField = objectBeanClass.getDeclaredField(filedName);
                        // 设置新的值
                        declaredField.setAccessible(true); // 开启访问权限
                        declaredField.set(newObjectBean, new String(newData.getData())); // 设置新值
                        declaredField.setAccessible(false); // 关闭访问权限

                        String className = newObjectBean.getClass().getSimpleName();
                        log.info("update {} filed {} 【oldData:{} -> newData:{}】 success!",className, filedName, new String(oldData.getData()), new String(newData.getData()));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                case NODE_CREATED:
                    // 获取变化数据的path路径
                    String dccOldValuePath = newData.getPath();
                    // 获取对应的bean
                    Object oldObjectBean =dccObjGroup.get(dccOldValuePath);
                    if (null == oldObjectBean) return;
                    try {
                        // 处理AOP代理类
                        Class<?> objectBeanClass = oldObjectBean.getClass();
                        if (AopUtils.isAopProxy(oldObjectBean))
                            objectBeanClass = AopUtils.getTargetClass(oldObjectBean);
                        // 获取字段名
                        String filedName = dccOldValuePath.substring(dccOldValuePath.lastIndexOf("/") + 1);
                        // 获取字段
                        Field declaredField = objectBeanClass.getDeclaredField(filedName);
                        // 设置新的值
                        String newFieldValue = new String(newData.getData());
                        declaredField.setAccessible(true); // 开启访问权限
                        declaredField.set(oldObjectBean, newFieldValue); // 设置新值
                        String className = oldObjectBean.getClass().getSimpleName();
                        declaredField.setAccessible(false); // 关闭访问权限
                        log.error("create {} filed {} 【data:{}】 success!",className, filedName, newFieldValue);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    break;
                case NODE_DELETED:
                    log.info("暂未实现 NODE_DELETED（节点删除）");
                    break;
                default:
                    break;
            }
        });
    }


    /**
     * 实现 BeanPostProcessor 接口的 postProcessAfterInitialization 方法
     * 可以获取bean对象的相关方法
     */
    @Override
    public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
        // 获取bean对象的 class
        Class<?> targetBeanClass = bean.getClass();
        Object targetBeanObject = bean;
        // 处理AOP代理类的情况
        if (AopUtils.isAopProxy(bean)) {
            targetBeanClass = AopUtils.getTargetClass(bean);
            targetBeanObject = AopProxyUtils.getSingletonTarget(bean);
        }

        // 获取对象的字段集
        Field[] declaredFields = targetBeanClass.getDeclaredFields();
        // 循环遍历 找出添加了 @DCCValue 注解的字段
        for (Field field : declaredFields) {
            if (!field.isAnnotationPresent(DCCValue.class)) continue;
            DCCValue dccValue = field.getAnnotation(DCCValue.class);
            String value = dccValue.value();
            if (null == value || value.isEmpty()) {
                throw new RuntimeException(field.getName() + " @DCCValue is not config value config case 「isSwitch/isSwitch:1」");
            }
            // @DCCValue(value = "switch:open") -> : 后面的是默认值
            String[] splits = value.split(":");
            String key = splits[0];
            // 获取DCCValue注解的值
            String defaultValue = splits.length == 2 ? splits[1] : null;
            // 拼接zk路径
            String keyPath = BASE_CONFIG_PATH_CONFIG.concat("/").concat(key);
            // 判断zk对应的路径节点是否已经有值
            try {
                Stat stat = zkClient.checkExists().forPath(keyPath);
                if (null == stat) {
                    // 没有值，则创建路径
                    zkClient.create().creatingParentsIfNeeded().forPath(keyPath);
                    // 设置默认值
                    if (null != defaultValue && !defaultValue.isEmpty()) {
                        // Java中，Field对象表示一个类的字段（成员变量）。
                        // 每个Field对象都有一个accessible属性，这个属性用来指示这个字段是否可以被访问。
                        // 默认情况下，这个属性的值取决于该字段的具体修饰符（如public, private等）：
                        //对于public字段，accessible默认为true。
                        //对于private字段，accessible默认为false。
                        field.setAccessible(true); // 开启访问权限
                        // 通过反射，设置字段值（设置哪个对象的？由参数bean指定）
                        field.set(targetBeanObject, defaultValue);
                        field.setAccessible(false); // 关闭访问权限
                        log.info("DCC 节点监听 创建节点 beanName:{} fieldName:{} defaultValue:{} keyPath:{}",beanName,field.getName(),defaultValue, keyPath);
                    }
                } else {
                    // 路径节点存在，则获取值并设置获取到的值给对应bean的字段
                    byte[] data = zkClient.getData().forPath(keyPath);
                    if (null != data && data.length > 0) {
                        String configValue = new String(data);
                        field.setAccessible(true); // 开启访问权限
                        field.set(targetBeanObject, configValue);
                        field.setAccessible(false); // 关闭访问权限
                        log.info("DCC 节点监听 设置配置 beanName:{} fieldName:{} defaultValue:{} keyPath:{}",beanName,field.getName(),configValue, keyPath);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            // 存储bean对象，供给构造函数使用
            dccObjGroup.put(keyPath, targetBeanObject);
        }
        return bean;
    }
}